﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Collections.Specialized;
using System.Web.Caching;

/*
 * DataCache使用指南
 * 1.用在DooPage上，在PreInit事件中使用EndIfInCache,在Render事件中使用SetCache即可
 * 2.建立依赖关系，为了使各类更新能有效的反映到缓存中去，建立依赖。此依赖靠手工。
 *  例如，文章更新时就调用ArticleCacheUpdate(此次更新所涉及的类型、值)。
 * 
 * 
 * 
 * 
 */
namespace Test
{
    using Doo.Caching;

    public enum ArticleUpdateType
    {
        Content,//内容
        Stat//统计数据
    }

    public class CachersByArticleCategory : Dictionary<string, List<Cacher<CacheParamType>>> { }
    public class CachersByUpdateType : Dictionary<ArticleUpdateType, CachersByArticleCategory> { }

    public class CacheUpdate
    {

        public static CachersByUpdateType articlecachers = new CachersByUpdateType();
        public static void ArticleUpdate(ArticleUpdateType type, string category)
        {
            if (articlecachers.ContainsKey(type))
            {
                CachersByArticleCategory csbac = articlecachers[type];
                if (csbac.ContainsKey(category))
                {
                    foreach (var cacher in csbac[category])
                    {
                        cacher.RemoveData();
                    }
                }
            }
        }
        public static void AddCacher(ArticleUpdateType type, string category, Cacher<CacheParamType> cacher)
        {
            if (!articlecachers.ContainsKey(type)) articlecachers.Add(type, new CachersByArticleCategory());
            CachersByArticleCategory csbac = articlecachers[type];
            if (!csbac.ContainsKey(category)) csbac.Add(category, new List<Cacher<CacheParamType>>());
            var cs = csbac[category];
            cs.Add(cacher);
        }
    }
}

namespace Doo.Caching
{
    public abstract class DataCacheProvider
    {
        /// <summary>
        /// 获取应写入到cookie中的键
        /// </summary>
        /// <returns></returns>
        public abstract string[] GetKeys();
    }

    public abstract class DataCacheProvider<TParamType> : DataCacheProvider
    {
        public HttpContext Context
        {
            get { return HttpContext.Current; }
        }

        /// <summary>
        /// 获取键值
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public virtual string GetKey(TParamType type)
        {
            string key = "~";
            int t = Convert.ToInt32(type);
            HttpContext context = Context;
            foreach (var entry in KeyGetters)
            {
                int k = Convert.ToInt32(entry.Key);
                if ((t & k) == 0) continue;
                var getter = KeyGetters[entry.Key];
                key += getter(context) + "~";
            }
            return key;
        }

        protected Dictionary<TParamType, Func<HttpContext, string>> KeyGetters;

        /// <summary>
        /// 在派生类重写时，初始化每个位对应的键值
        /// </summary>
        public abstract void InitKeyGetter();
    }

    #region DataCache<TParamType>
    public class DataCache<TParamType>
    {
        Dictionary<string, Dictionary<string, Cacher<TParamType>>> cachers = new Dictionary<string, Dictionary<string, Cacher<TParamType>>>();
        Dictionary<string, long> Versions = new Dictionary<string, long>();

        DataCacheProvider<TParamType> provider;

        private static DataCache<TParamType> _instance;
        public static DataCache<TParamType> Instance
        {
            get
            {
                return _instance;
            }
        }
        public static void Init(DataCacheProvider<TParamType> provider)
        {
            _instance = new DataCache<TParamType>(provider);
        }

        public DataCache(DataCacheProvider<TParamType> provider)
        {
            this.provider = provider;
        }

        public long GetVersion(TParamType paramType)
        {
            string key = provider.GetKey(paramType);
            if (Versions.ContainsKey(key)) return Versions[key];
            else return -1;
        }

        internal long NewVersion(string cachekey)
        {
            if (Versions.ContainsKey(cachekey))
            {
                return ++Versions[cachekey];
            }
            else
            {
                Versions.Add(cachekey, 0);
                return 0;
            }
        }

        public void SetVersionCookie(HttpContext context)
        {
            HttpCookieCollection cookies_request = context.Request.Cookies;
            HttpCookieCollection cookies_response = context.Response.Cookies;
            //string[] keys
            foreach (var entry in Versions)
            {
                HttpCookie c = cookies_request[entry.Key];
                if (c == null || c.Value != entry.Value.ToString())
                {
                    HttpCookie s = cookies_response[entry.Key];
                    s.Value = entry.Value.ToString();
                }
            }
        }

        public Cacher<TParamType> GetCacher(string key, TParamType paramType)
        {
            Dictionary<string, Cacher<TParamType>> crs;
            Cacher<TParamType> cr;
            HttpContext context = HttpContext.Current;
            string cachekey = provider.GetKey(paramType);

            if (!cachers.TryGetValue(cachekey, out crs))
            {
                crs = new Dictionary<string, Cacher<TParamType>>();
                cachers.Add(cachekey, crs);
            }

            if (!crs.TryGetValue(key, out cr))
            {
                if (!Versions.ContainsKey(cachekey)) Versions.Add(cachekey, 0);
                long version = Versions[cachekey];
                cr = new Cacher<TParamType>(cachekey + "-" + key, paramType, version, cachekey);
                crs.Add(key, cr);
                return cr;
            }
            return cr;
        }

        public object GetData(HttpContext context, string key)
        {
            return context.Cache[key];
        }

        public void SetData(HttpContext context, string key, object data)
        {
            context.Cache[key] = data;
        }
    }
    #endregion

    #region Cacher<TParamType>
    public class Cacher<TParamType>
    {
        private long _version = 0;
        public long Version { get { return _version; } }

        private bool _hasData;
        /// <summary>
        /// 缓存中是否有数据（未过期）
        /// </summary>
        public bool HasData
        {
            get
            {
                return _hasData;
            }
        }

        /// <summary>
        /// 获取已填充的参数键值
        /// </summary>
        public string ParamTypeKey { get { return typekey; } }

        private string key;
        private TParamType type;
        private string typekey;
        public Cacher(string key, TParamType type, long version, string typekey)
        {
            this.key = key;
            this.type = type;
            this._version = version;
            this.typekey = typekey;
        }

        public object GetData()
        {
            return DataCache<TParamType>.Instance.GetData(HttpContext.Current, key);
        }

        public void SetData(object data)
        {
            //更新数据版本
            _version = DataCache<TParamType>.Instance.NewVersion(typekey);
            _hasData = true;
            DataCache<TParamType>.Instance.SetData(HttpContext.Current, key, data);
        }

        public void RemoveData()
        {
            _hasData = false;
            DataCache<TParamType>.Instance.SetData(HttpContext.Current, key, null);
        }
    }
    #endregion

    #region CacheParamType
    /// <summary>
    /// 一个类别实例，分类为：无、组、会员、浏览者、浏览者组
    /// </summary>
    [Flags]
    public enum CacheParamType
    {
        None = 0x00,
        Group = 0x01,
        Member = 0x02,
        Visitor = 0x04,
        VisitorGroup = 0x08
    }
    #endregion

    public class ttt : DataCacheProvider<CacheParamType>
    {
        string GetMemberKey(HttpContext context)
        {
            return context.Items["PersonalCode"].ToString();
        }

        string GetVisitorKey(HttpContext context)
        {
            return context.Items["HostCode"].ToString();
        }

        public override void InitKeyGetter()
        {
            KeyGetters = new Dictionary<CacheParamType, Func<HttpContext, string>>
                {
                    {CacheParamType.Member, GetMemberKey},
                    {CacheParamType.Visitor,GetVisitorKey}
                };
        }

        public override string[] GetKeys()
        {
            HttpContext context = Context;
            return new string[]
                {
                    GetMemberKey(context),
                    GetVisitorKey(context)
                };
        }
    }

    public interface IContextCacheable<TParamType>
    {
        bool EnableCache { get; set; }
        TParamType CacheParamType { get; set; }
        HttpRequest Request { get; }
        HttpResponse Response { get; }
    }

    /// <summary>
    /// 上下文缓存
    /// </summary>
    /// <typeparam name="TParamType"></typeparam>
    public class ContextCache<TParamType>
    {
        private static ContextCache<TParamType> _instance;
        public static ContextCache<TParamType> Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new ContextCache<TParamType>();
                    _instance._cache = DataCache<TParamType>.Instance;
                }
                return _instance;
            }
        }

        private DataCache<TParamType> _cache;
        protected ContextCache() { }

        public void EndIfInCache(IContextCacheable<TParamType> context)
        {
            string key = context.Request.AppRelativeCurrentExecutionFilePath;
            var cacher = _cache.GetCacher(key, context.CacheParamType);
            if (!cacher.HasData) return;
            string oldetag = context.Request.Headers["If-None-Match"];
            string ver = cacher.ParamTypeKey + "~" + cacher.Version.ToString();
            if (key == ver)
            {
                context.Response.StatusCode = 304;
                context.Response.End();
            }
            else
            {
                string data = cacher.GetData().ToString();
                SetETag(context.Response.Cache, ver);
                context.Response.Write(data);
                context.Response.End();
            }
        }

        public void SetCache(IContextCacheable<TParamType> page,string content)
        {
            string key = page.Request.AppRelativeCurrentExecutionFilePath;
            var cacher = _cache.GetCacher(key, page.CacheParamType);
            cacher.SetData(content);
            string ver = cacher.ParamTypeKey + "~" + cacher.Version.ToString();
            SetETag(page.Response.Cache, ver);
        }

        static void SetETag(HttpCachePolicy cache,string etag)
        {
            cache.SetCacheability(HttpCacheability.Public);
            cache.SetMaxAge(TimeSpan.FromMinutes(1));
            cache.SetSlidingExpiration(true);
            cache.SetETag(etag);
        }
    }
}
